﻿//
// $HeadURL: http://codeka-memcache.googlecode.com/svn/trunk/Codeka.Memcache/Commands/PacketHeader.cs $
// $Revision: 5 $
// $Author: dean.codeka $     $Date: 2010-02-19 22:52:37 +0000 (Fri, 19 Feb 2010) $
//
// Copyright © 2009-2010 Dean Harding, all rights reserved.
//

using System.IO;
using System.Net;
namespace Codeka.Memcache.Commands
{
    /// <summary>
    /// A packet is either a request or a request (and we have to send a different
    /// magic byte for each).
    /// </summary>
    public enum PacketType : byte
    {
        Request = 0x80,
        Response = 0x81
    }

    /// <summary>
    /// Gets or sets the memcache "opcode" of this packet, that is, what we're
    /// actually supposed to <em>do</em>.
    /// </summary>
    public enum PacketOpCode : byte
    {
        Get = 0x00,
        Set = 0x01,
        Add = 0x02,
        Replace = 0x03,
        Delete = 0x04,
        Increment = 0x05,
        Decrement = 0x06,
        Quit = 0x07,
        Flush = 0x08,
        GetQuiet = 0x09,
        Noop = 0x0A,
        Version = 0x0B,
        GetWithKey = 0x0C,
        GetWithKeyQuiet = 0x0D,
        Append = 0x0E,
        Prepend = 0x0F,
        Stat = 0x10,
        SetQuiet = 0x11,
        AddQuiet = 0x12,
        ReplaceQuiet = 0x13,
        DeleteQuiet = 0x14,
        IncrementQuiet = 0x15,
        DecrementQuiet = 0x16,
        QuitQuiet = 0x17,
        FlushQuiet = 0x18,
        AppendQuiet = 0x19,
        PrependQuiet = 0x1A,
    }

    /// <summary>
    /// These are the status codes that we know about.
    /// </summary>
    public enum StatusCodes : short
    {
        NoError = 0x0000,
        KeyNotFound = 0x0001,
        KeyExists = 0x0002,
        ValueTooLarge = 0x0003,
        InvalidArguments = 0x0004,
        ItemNotStored = 0x0005,
        NonNumericValue = 0x0006,
    }

    /// <summary>
    /// Represets the contents of the header for a binary memcache command.
    /// </summary>
    public class PacketHeader
    {
        /// <summary>
        /// Gets or sets whether this is a <b>Request</b> or <b>Response</b> packet.
        /// </summary>
        public PacketType PacketType { get; set; }

        /// <summary>
        /// Gets or sets the <see cref="PacketOpCode"/> which describes <em>what to do</em>.
        /// </summary>
        public PacketOpCode OpCode { get; set; }

        /// <summary>
        /// Gets or sets the length of the key in this packet.
        /// </summary>
        public short KeyLength { get; set; }

        /// <summary>
        /// Gets or sets the length of the "extra" data (if any).
        /// </summary>
        public byte ExtrasLength { get; set; }

        /// <summary>
        /// A one byte field that you can use to determine the "type" of the
        /// serialized data.
        /// </summary>
        public byte DataType { get; set; }

        /// <summary>
        /// When <see cref="PacketType"/> is <b>Response</b>, this will be the status code
        /// (i.e. error indicator). Non-zero means an error occured.
        /// </summary>
        public StatusCodes StatusCode { get; set; }

        /// <summary>
        /// Gets or sets the combined body length (extras + key + value).
        /// </summary>
        public int TotalBodyLength { get; set; }

        /// <summary>
        /// Gets or sets an "opaque" value which has no meaning to memcache (or
        /// to <see cref="Codeka.Memcache"/>) and you can use to store "extra" data
        /// with the key.
        /// </summary>
        public int Opaque { get; set; }

        /// <summary>
        /// Gets or sets the 64-bit check-and-set flag.
        /// </summary>
        public long CasValue { get; set; }

        /// <summary>
        /// The length, in bytes, of the header when it's been serialized.
        /// </summary>
        public const int SerializedLength = 20;

        /// <summary>
        /// Serialize this <see cref="PacketHeader"/> to the given <see cref="BinaryWriter"/>.
        /// </summary>
        public void Serialize(BinaryWriter writer)
        {
            writer.Write((byte)PacketType);
            writer.Write((byte)OpCode);
            writer.Write(IPAddress.HostToNetworkOrder(KeyLength));
            writer.Write(ExtrasLength);
            writer.Write(DataType);
            writer.Write(IPAddress.HostToNetworkOrder((short)StatusCode));
            writer.Write(IPAddress.HostToNetworkOrder(TotalBodyLength));
            writer.Write(IPAddress.HostToNetworkOrder(Opaque));
            writer.Write(IPAddress.HostToNetworkOrder(CasValue));
        }

        /// <summary>
        /// Deserialize this <see cref="PacketHeader"/> from the given <see cref="BinaryWriter"/>.
        /// </summary>
        public void Deserialize(BinaryReader reader)
        {
            PacketType = (PacketType)reader.ReadByte();
            OpCode = (PacketOpCode)reader.ReadByte();
            KeyLength = IPAddress.NetworkToHostOrder(reader.ReadInt16());
            ExtrasLength = reader.ReadByte();
            DataType = reader.ReadByte();
            StatusCode = (StatusCodes)IPAddress.NetworkToHostOrder(reader.ReadInt16());
            TotalBodyLength = IPAddress.NetworkToHostOrder(reader.ReadInt32());
            Opaque = IPAddress.NetworkToHostOrder(reader.ReadInt32());
            CasValue = IPAddress.NetworkToHostOrder(reader.ReadInt64());
        }
    }
}
